在上一篇我們了解指令是如何從封裝後客戶端發送到伺服器,我們依舊不清楚 Unlight 是如何將指令的內容轉換成可以被程式執行的動作。因此我們還需要了解 Unlight 是如何「執行」指令的。
先回來觀察看完整的 #receive_data
方法
# データの受信
def receive_data data
a = data2command(data)
@command_list += a unless a.empty?
do_command
end
當 #data2command
解析完畢結構後,會將指令儲放到 @command_list
陣列裡面,然後接著呼叫 #do_command
方法來嘗試執行指令,因此我們要來看一下 #do_command
做了什麼才能讓 Unlight 執行指令上的動作。
# コマンドの実行
def do_command
while cmd =@command_list.shift
if cmd[0] > @func_list.size
SERVER_LOG.error("#{@@class_name}: [invalid comanndNo.] >> #{cmd[0]}")
# エラー数をカウントして
if @error_count > COMMAND_ERROR_MAX
SERVER_LOG.error("#{@@class_name}: [ErrorMAX disconnect!] >> #{cmd[0]}")
logout
else
@error_count +=1
end
else
begin
@func_list[cmd[0]].call(cmd[1])
rescue =>e
SERVER_LOG.fatal("#{@@class_name}: [docommand:] fatal error #{e}:#{e.backtrace}")
end
end
end
end
在 #do_command
方法中,會先嘗試將 @command_list
陣列裡面存放的指令盡可能的依序讀取到沒有新的指令為止,假設一次收到多個指令的時候才不會一時間少處理或者太慢處理。接下來會做一些檢查,像是用 cmd[0] > @func_list.size
會檢查指令是否存在。
如果指令確實存在的話,就會跑到 @func_list[cmd[0]].call(cmd[1])
這段程式碼,透過 #call
將對應的方法執行,如果不存在的話則會紀錄錯誤的次數,太多次的話就判斷為有問題的使用者將它踢出伺服器。
關於
@func_list
我們會在後續登記可用指令的部分繼續討論。至於#call
方法是 Ruby 的語言特性之一,有點類似我們在 C 與原使用的 Function Pointer 或者某些語言的 Func 類型物件,在前面我們有提到 Proc 物件就是這類物件的原型。
不過 cmd
這個來自於 #data2command
的變數又代表了怎樣的資訊,目前我們只知道他會有 cmd[0]
和 cmd[1]
兩個元素,但是分別是什麼呢?
在 #data2command
這個方法中,我們會看到當指令到達結尾符號 \n
的時候,會將一些資訊存到 a
裡面,最後 #data2command
會回傳這個 a
陣列,而在文章中一開始的地方提到 #data2command
處理後的結果則是會被加入到 @command_list
陣列中。
if data[i+len+2] == "\n"
d = @crypt.decrypt(data[i+2,len])
a << [d[0,2].unpack('n')[0], d[2..-1]]
end
也就是說 [d[0, 2].unpack('n')[0], d[2..-1]
就是對應 cmd[0]
和 cmd[1]
的數值,前面我們已經知道 Command ID 是一個 2 Bytes 的整數,因此 d[0, 2].unpack('n')[0]
的值就會是指令的編號,而剩下的部分直到結尾符號 \n
則會是 cmd[1]
的值,也就是指令的內容部分。
到這邊為止,我們大致上已經知道從客戶端發送資料到伺服器之後,是如何解析跟執行。但是該執行哪個指令跟執行的細節還不清楚,這就要再討論 Unlight 的 Command
系列物件是怎麼針對不同的伺服器登記可用的指令。
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。